package pub.cleangao.mybatis.springboot;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import pub.cleangao.common.util.ReflectUtil;
import pub.cleangao.mybatis.annotation.Table;
import pub.cleangao.mybatis.cleavage.Cleavage;
import pub.cleangao.mybatis.cleavage.UseCleavage;
import pub.cleangao.mybatis.meta.ColumnDefinition;
import pub.cleangao.mybatis.meta.TableDefinition;
import pub.cleangao.mybatis.runtime.MybatisContext;
import pub.cleangao.mybatis.util.ClassUtil;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Aspect
@Order(Integer.MAX_VALUE - 1)
@Component
public class MyBatisMapperAspect {


    @Pointcut("within(pub.cleangao.mybatis.dao.CrudMapper+) || @annotation(org.apache.ibatis.annotations.Mapper)")
    public void methodPointcut() {
    }

    @Around(value = "methodPointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Class<?> proxyClass = point.getTarget().getClass();
        Class<?>[] interfaces = proxyClass.getInterfaces();
        Class<?> mapperClass = interfaces[0];
        TableDefinition tableDefinition = MybatisContext.getMapperTableDefinition(mapperClass);
        // 当前全部的参数
        Object[] args = point.getArgs();
        // 1. 优先使用表上的注解
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        UseCleavage useCleavage = method.getAnnotation(UseCleavage.class);
        if (useCleavage != null) {
            Class<? extends Cleavage> cleavageClass = useCleavage.cleavageClass();
            Cleavage cleavage = ClassUtil.newInstance(cleavageClass);
            String[] paramBy = useCleavage.paramNameBy();
            if (paramBy != null && paramBy.length > 0) {
                Object[] paramValBy = new Object[paramBy.length];
                Parameter[] params = method.getParameters();
                for (int i = 0; i < paramBy.length; i++) {
                    String paramTargetName = paramBy[i];
                    for (int j = 0; i < params.length; j++) {
                        Parameter parameter = params[j];
                        if (paramTargetName.equals(parameter.getName())) {
                            paramValBy[i] = args[j];
                        }
                    }
                }
                if (!Arrays.asList(paramValBy).isEmpty()) {
                    return proceedWithCleavage(point, tableDefinition, cleavage, paramValBy);
                }
            }
        }
        // 2. 然后使用表定义tableDefinition配置
        if (!tableDefinition.getCleavage()) {
            return point.proceed();
        }
        Class<?> entityClass = MybatisContext.getMapperEntityClass(mapperClass);
        Table tableAnnotation = entityClass.getAnnotation(Table.class);
        if (tableAnnotation == null || !tableAnnotation.cleavage()) {
            return point.proceed();
        }
        if (tableAnnotation.cleavageClass() == null) {
            return point.proceed();
        }
        Cleavage cleavage = ClassUtil.newInstance(tableAnnotation.cleavageClass());
        int argIndex = -1;
        for (int i = 0; i < args.length; i++) {
            Object obj = args[i];
            if (obj.getClass().getName().equals(entityClass.getName())) {
                argIndex = i;
                break;
            }
        }
        if (argIndex < 0) {
            return point.proceed();
        }
        Object targetArg = args[argIndex];
        List<ColumnDefinition> columnDefinitions = tableDefinition.getCleavageColumnBy();
        List<String> columns = columnDefinitions.stream()
                .filter(o -> o.getCleavageParamIndex() >= 0)
                .sorted((o1, o2) -> o1.getCleavageParamIndex() - o2.getCleavageParamIndex())
                .map(ColumnDefinition::getFieldName).collect(Collectors.toList());
        Object[] cleavageArgs = new Object[columns.size()];
        for (int i = 0; i < columns.size(); i++) {
            cleavageArgs[i] = ReflectUtil.getFieldValue(targetArg, columns.get(i));
        }
        return proceedWithCleavage(point, tableDefinition, cleavage, cleavageArgs);

    }

    private Object proceedWithCleavage(ProceedingJoinPoint point, TableDefinition tableDefinition, Cleavage cleavage, Object[] cleavageArgs) throws Throwable {
        String newDataSourceKey = cleavage.newDataSourceKey(tableDefinition.getDataSourceKey(), cleavageArgs);
        String newTableName = cleavage.newTableName(tableDefinition.getTableName(), cleavageArgs);
        DataSourceContext.setDataSourceKey(newDataSourceKey);
        DataSourceContext.setTableName(tableDefinition.getTableName(), newTableName);
        try {
            return point.proceed();
        } finally {
            DataSourceContext.clearDataSourceKey();
            DataSourceContext.clearNewTableName();
        }
    }

}
